package net.luohuasheng.bee.proxy.core.utils.jpa;

import net.luohuasheng.bee.proxy.core.enums.DataType;
import net.luohuasheng.bee.proxy.core.utils.ColumnUtils;
import net.luohuasheng.bee.proxy.core.utils.StringUtils;
import net.luohuasheng.bee.proxy.core.utils.classloader.dto.ClassMethodDto;
import net.luohuasheng.bee.proxy.core.utils.jpa.annotation.ColumnExtra;
import net.luohuasheng.bee.proxy.core.utils.jpa.annotation.TableLogic;
import net.luohuasheng.bee.proxy.core.utils.jpa.dto.JpaColumnDto;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Transient;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * @author luohuasheng
 * @date 2020/4/19 21:03
 */
public class JpaLoadUtils {

    public static void loadJpaColumn(ClassMethodDto<JpaColumnDto> classMethodDto, Method readMethod, Method writeMethod, Field field) {
        if (isNotTransient(readMethod, writeMethod, field)) {
            loadColumn(classMethodDto, readMethod, writeMethod, field);
            if (classMethodDto.getColumn() != null) {
                loadGeneratedValue(classMethodDto, readMethod, writeMethod, field);
                loadColumnExtra(classMethodDto, readMethod, writeMethod, field);
                Id id = getAnnotation(readMethod, writeMethod, field, Id.class);
                if (id != null) {
                    classMethodDto.getColumn().setPk(true);
                }
            }
        } else {
            classMethodDto.setTransient(true);
        }
    }

    private static boolean isNotTransient(Method readMethod, Method writeMethod, Field field) {
        return !Modifier.isTransient(field.getModifiers()) && getAnnotation(readMethod, writeMethod, field, Transient.class) == null;
    }

    private static void loadColumnExtra(ClassMethodDto<JpaColumnDto> classMethodDto, Method readMethod, Method writeMethod, Field field) {
        ColumnExtra columnExtra = getAnnotation(readMethod, writeMethod, field, ColumnExtra.class);
        if (columnExtra != null && classMethodDto.getColumn() != null) {
            if (!StringUtils.isEmpty(columnExtra.defaultValue())) {
                classMethodDto.getColumn().setDefaultValue(columnExtra.defaultValue());
            }
            if (!StringUtils.isEmpty(columnExtra.comment())) {
                classMethodDto.getColumn().setComment(columnExtra.comment());
            }
        }
    }

    private static void loadGeneratedValue(ClassMethodDto<JpaColumnDto> classMethodDto, Method readMethod, Method writeMethod, Field field) {
        GeneratedValue generatedValue = getAnnotation(readMethod, writeMethod, field, GeneratedValue.class);
        if (generatedValue != null && classMethodDto.getColumn() != null) {
            classMethodDto.getColumn().setStrategy(generatedValue.strategy());
            if (!StringUtils.isEmpty(generatedValue.generator())) {
                classMethodDto.getColumn().setGenerator(generatedValue.generator());
            }
        }
    }

    private static void loadColumn(ClassMethodDto<JpaColumnDto> classMethodDto, Method readMethod, Method writeMethod, Field field) {
        Column column = getAnnotation(readMethod, writeMethod, field, Column.class);
        if (column != null) {
            classMethodDto.setColumn(loadColumnAttribute(column));
            classMethodDto.getColumn().setField(field);
            DataType dataType = DataType.getTypeByJavaType(field.getType(), false).stream().min((o1, o2) -> o2.getMaxSize() - o1.getMaxSize()).orElse(DataType.BLOB);
            classMethodDto.getColumn().setDataType(dataType);
            classMethodDto.getColumn().setInsertable(column.insertable());
            classMethodDto.getColumn().setUpdatable(column.updatable());
            if (dataType.isHasDigits()) {
                if (classMethodDto.getColumn().getLength() - classMethodDto.getColumn().getScale() > dataType.getMaxSize()) {
                    classMethodDto.getColumn().setLength(dataType.getMaxSize());
                }
            } else {
                if (classMethodDto.getColumn().getLength() > dataType.getMaxSize()) {
                    classMethodDto.getColumn().setLength(dataType.getMaxSize());
                }
            }
            if ("".equals(column.name())) {
                classMethodDto.setProperty(field.getName());

            } else {
                classMethodDto.setProperty(column.name());
            }
            if (StringUtils.isEmpty(classMethodDto.getColumn().getName())) {
                classMethodDto.getColumn().setName(ColumnUtils.toSnakeCase(classMethodDto.getProperty()));
            }
        } else {
            if (isNotTransient(readMethod, writeMethod, field)) {
                JpaColumnDto jpaColumnDto = new JpaColumnDto();
                jpaColumnDto.setName(ColumnUtils.toSnakeCase(field.getName()));
                DataType dataType = DataType.getDefaultType(field.getType());
                jpaColumnDto.setDataType(dataType);
                jpaColumnDto.setNullable(true);
                jpaColumnDto.setInsertable(true);
                jpaColumnDto.setUpdatable(true);
                jpaColumnDto.setLength(dataType.isHasDigits() ? dataType.getMaxSize() + 6 : dataType.getMaxSize());
                jpaColumnDto.setScale(dataType.isHasDigits() ? 6 : 0);
                jpaColumnDto.setField(field);
                classMethodDto.setColumn(jpaColumnDto);
            }
            classMethodDto.setProperty(field.getName());
        }
        TableLogic tableLogic = getAnnotation(readMethod, writeMethod, field, TableLogic.class);
        if (tableLogic != null) {
            classMethodDto.getColumn().setTableLogic(tableLogic);
        }

    }

    public static <T extends Annotation> T getAnnotation(Method readMethod, Method writeMethod, Field field, Class<T> classZ) {
        T column = field.getAnnotation(classZ);
        if (column == null) {
            column = readMethod.getAnnotation(classZ);
            if (column == null) {
                column = writeMethod.getAnnotation(classZ);
            }
        }
        return column;
    }

    public static JpaColumnDto loadColumnAttribute(Column column) {
        JpaColumnDto jpaColumnDto = new JpaColumnDto();
        for (Field declaredField : JpaColumnDto.class.getDeclaredFields()) {
            declaredField.setAccessible(true);
            for (Method method : Column.class.getMethods()) {
                if (method.getName().equals(declaredField.getName())) {
                    try {
                        declaredField.set(jpaColumnDto, method.invoke(column));
                    } catch (IllegalAccessException | InvocationTargetException ignored) {
                    }
                }
            }
        }
        if (column.precision() != 0) {
            jpaColumnDto.setLength(column.precision());
        }
        return jpaColumnDto;
    }
}
